/*
 * Copyright (C) 2020 xuexiangjys(xuexiangjys@163.com)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

package com.xuexiang.xui_lib.component.button;

import ohos.agp.animation.AnimatorValue;
import ohos.agp.colors.RgbColor;
import ohos.agp.components.AttrSet;
import ohos.agp.components.Component;
import ohos.agp.render.Canvas;
import ohos.agp.render.Paint;
import ohos.agp.render.Path;
import ohos.agp.utils.Color;
import ohos.agp.utils.Point;
import ohos.app.Context;
import ohos.eventhandler.EventHandler;
import ohos.eventhandler.EventRunner;
import ohos.hiviewdfx.HiLog;
import ohos.hiviewdfx.HiLogLabel;

import com.xuexiang.xui_lib.util.AttrValue;

/**
 * 自带动画的checkbox
 *
 * @since 2021-04-21
 */
public class SmoothCheckBox extends Component implements Component.DrawTask, Component.EstimateSizeListener {
    private static final HiLogLabel TAG = new HiLogLabel(3, 0xD001100, " ");

    private static final int COLOR_FLOOR_UNCHECKED = Color.getIntColor("#DFDFDF");

    private static final int DEF_DRAW_SIZE = 25;
    private static final int DEF_ANIM_DURATION = 300;
    private static final float TWO = 2;
    private static final int THREE = 3;
    private static final int FIVE = 5;
    private static final int SEVEN = 7;
    private static final int TEN_INT = 10;
    private static final int THIRTEEN = 13;
    private static final int FOURTEEN = 14;
    private static final int TWENTY_INT = 20;
    private static final int TWEBTY_TWO = 22;
    private static final int THIRTY = 30;
    private static final float ZERO_POINT_FIVE = 0.5f;
    private static final float ZERO_POINT_EIGHT = 0.8f;
    private static final float TWENTY = 20.0f;

    private Paint mPaint;
    private Paint mTickPaint;
    private Paint mFloorPaint;
    private Point[] mTickPoints;
    private Point mCenterPoint;
    private Path mTickPath;

    private float mLeftLineDistance;
    private float mRightLineDistance;
    private float mDrewDistance;
    private float mScaleVal = 1.0f;
    private float mFloorScale = 1.0f;
    private int mWidth;
    private int mAnimDuration;
    private int mStrokeWidth;
    private int mCheckedColor;
    private int mUnCheckedColor;
    private int mFloorColor;
    private int mFloorUnCheckedColor;

    private boolean mChecked;
    private boolean mTickDrawing;
    private OnCheckedChangeListener mOnCheckedChangeListener;

 /*   *//**
     * 构造方法
     *
     * @param context 上下文
     *//*
    public SmoothCheckBox(Context context) {
        this(context, null);
    }*/

    /**
     * 构造方法
     *
     * @param context 上下文
     * @param attrSet 参数值
     */
    public SmoothCheckBox(Context context, AttrSet attrSet) {
        this(context, attrSet, null);
    }

    /**
     * 构造方法
     *
     * @param context 上下文
     * @param attrSet 参数值
     * @param styleName 扩展参数
     */
    public SmoothCheckBox(Context context, AttrSet attrSet, String styleName) {
        super(context, attrSet, styleName);
        initAttrs(context, attrSet, styleName);
        HiLog.info(TAG, "创建View");
    }

    /**
     * 初始化
     *
     * @param context 上下文
     * @param attrSet 属性参数
     * @param styleName 扩展参数
     */
    private void initAttrs(Context context, AttrSet attrSet, String styleName) {
        int tickColor = AttrValue.get(attrSet, "scb_color_tick", new Color(Color.WHITE.getValue())).getValue();
        mAnimDuration = AttrValue.get(attrSet, "scb_duration", DEF_ANIM_DURATION);
        mFloorColor = AttrValue.get(attrSet, "scb_color_unchecked_stroke",
            new Color(COLOR_FLOOR_UNCHECKED)).getValue();

        // new Color(context.getColor(ResourceTable.Color_colorAccent))
        mCheckedColor = AttrValue.get(attrSet, "scb_color_checked",
            new Color(Color.getIntColor("#299EE3"))).getValue();
        mUnCheckedColor = AttrValue.get(attrSet, "scb_color_unchecked",
            new Color(Color.WHITE.getValue())).getValue();
        mStrokeWidth = AttrValue.getDimension(attrSet, "scb_stroke_width", 0);

        mFloorUnCheckedColor = mFloorColor;
        mTickPaint = new Paint();
        mTickPaint.setStyle(Paint.Style.STROKE_STYLE);
        mTickPaint.setStrokeCap(Paint.StrokeCap.ROUND_CAP);
        mTickPaint.setColor(new Color(tickColor));

        mFloorPaint = new Paint();
        mFloorPaint.setStyle(Paint.Style.FILL_STYLE);
        mFloorPaint.setColor(new Color(mFloorColor));

        mPaint = new Paint();
        mPaint.setStyle(Paint.Style.FILL_STYLE);
        mPaint.setColor(new Color(mCheckedColor));

        mTickPath = new Path();
        mCenterPoint = new Point();
        mTickPoints = new Point[THREE];
        mTickPoints[0] = new Point();
        mTickPoints[1] = new Point();
        mTickPoints[2] = new Point();

        setClickedListener(new ClickedListener() {
            @Override
            public void onClick(Component component) {
                toggle();
                mTickDrawing = false;
                mDrewDistance = 0;
                if (isChecked()) {
                    startCheckedAnimation();
                } else {
                    startUnCheckedAnimation();
                }
            }
        });
        setEstimateSizeListener(this);
        addDrawTask(this);
    }

    /**
     * 控件开关
     */
    public void toggle() {
        this.setChecked(!isChecked());
    }

    /**
     * 静默设置
     *
     * @param checked
     */
    public void setCheckedSilent(boolean checked) {
        mChecked = checked;
        reset();
        invalidate();
    }

    /**
     * 设置控件状态
     *
     * @param checked 选中状态
     */
    public void setChecked(boolean checked) {
        mChecked = checked;
        reset();

        // invalidate(); 先保留
        if (mOnCheckedChangeListener != null) {
            mOnCheckedChangeListener.onCheckedChanged(SmoothCheckBox.this, mChecked);
        }
    }

    /**
     * checked with animation
     *
     * @param checked checked
     * @param animate change with animation
     */
    public void setChecked(boolean checked, boolean animate) {
        if (animate) {
            mTickDrawing = false;
            mChecked = checked;
            mDrewDistance = 0f;
            if (checked) {
                startCheckedAnimation();
            } else {
                startUnCheckedAnimation();
            }
            if (mOnCheckedChangeListener != null) {
                mOnCheckedChangeListener.onCheckedChanged(SmoothCheckBox.this, mChecked);
            }
        } else {
            this.setChecked(checked);
        }
    }

    private void reset() {
        mTickDrawing = true;
        mFloorScale = 1.0f;
        mScaleVal = isChecked() ? 0f : 1.0f;
        mFloorColor = isChecked() ? mCheckedColor : mFloorUnCheckedColor;
        mDrewDistance = isChecked() ? (mLeftLineDistance + mRightLineDistance) : 0;
    }

    public boolean isChecked() {
        return mChecked;
    }

    private void startCheckedAnimation() {
        AnimatorValue animator = new AnimatorValue();
        animator.setDuration((mAnimDuration / THREE * 2));
        animator.setValueUpdateListener(new AnimatorValue.ValueUpdateListener() {
            @Override
            public void onUpdate(AnimatorValue animatorValue, float value) {
                // 动画回调
                mScaleVal = 1 - value;
                HiLog.info(TAG, "startCheckedAnimation mScaleVal = " + mScaleVal);
                mFloorColor = getGradientColor(mUnCheckedColor, mCheckedColor, 1 - mScaleVal);
                invalidate();
            }
        });
        animator.start();

        AnimatorValue floorAnimator = new AnimatorValue();
        floorAnimator.setDuration(mAnimDuration);
        floorAnimator.setValueUpdateListener(new AnimatorValue.ValueUpdateListener() {
            @Override
            public void onUpdate(AnimatorValue animatorValue, float value) {
                // 动画回调 0-1
                if (value <= ZERO_POINT_FIVE) {
                    mFloorScale = 1 - (value / FIVE * TWO);
                } else {
                    mFloorScale = ZERO_POINT_EIGHT + ((value - ZERO_POINT_FIVE) / FIVE * TWO);
                }
                HiLog.info(TAG, "startCheckedAnimation mFloorScale = " + mFloorScale);
                invalidate();
            }
        });
        floorAnimator.start();

        drawTickDelayed();
    }

    private void drawTickDelayed() {
        new EventHandler(EventRunner.getMainEventRunner()).postTask(new Runnable() {
            @Override
            public void run() {
                mTickDrawing = true;
                invalidate();
            }
        }, mAnimDuration);
    }

    private void startUnCheckedAnimation() {
        AnimatorValue animator = new AnimatorValue();
        animator.setDuration(mAnimDuration);
        animator.setValueUpdateListener(new AnimatorValue.ValueUpdateListener() {
            @Override
            public void onUpdate(AnimatorValue animatorValue, float value) {
                // 动画回调
                mScaleVal = value;
                HiLog.info(TAG, "startUnCheckedAnimation mScaleVal = " + mScaleVal);
                mFloorColor = getGradientColor(mCheckedColor, mFloorUnCheckedColor, mScaleVal);
                invalidate();
            }
        });
        animator.start();

        AnimatorValue floorAnimator = new AnimatorValue();
        floorAnimator.setDuration(mAnimDuration);
        floorAnimator.setValueUpdateListener(new AnimatorValue.ValueUpdateListener() {
            @Override
            public void onUpdate(AnimatorValue animatorValue, float value) {
                // 动画回调
                if (value <= ZERO_POINT_FIVE) {
                    mFloorScale = 1 - (value / FIVE * TWO);
                } else {
                    mFloorScale = ZERO_POINT_EIGHT + ((value - ZERO_POINT_FIVE) / FIVE * TWO);
                }
                HiLog.info(TAG, "startUnCheckedAnimation mFloorScale = " + mFloorScale);
                invalidate();
            }
        });
        floorAnimator.start();
    }

    private static int getGradientColor(int startColor, int endColor, float percent) {
        int startA = Color.alpha(startColor);
        int startR = RgbColor.fromArgbInt(startColor).getRed();
        int startG = RgbColor.fromArgbInt(startColor).getGreen();
        int startB = RgbColor.fromArgbInt(startColor).getBlue();

        int endA = Color.alpha(endColor);

        int endR = RgbColor.fromArgbInt(endColor).getRed();
        int endG = RgbColor.fromArgbInt(endColor).getGreen();
        int endB = RgbColor.fromArgbInt(endColor).getBlue();

        int currentA = (int) (startA * (1 - percent) + endA * percent);
        int currentR = (int) (startR * (1 - percent) + endR * percent);
        int currentG = (int) (startG * (1 - percent) + endG * percent);
        int currentB = (int) (startB * (1 - percent) + endB * percent);
        HiLog.info(TAG, "currentR = " + currentR + "  currentG = " + currentG + "   currentB = " + currentB);
        HiLog.info(TAG, "current color = " + Color.argb(currentA, currentR, currentG, currentB));
        return Color.argb(currentA, currentR, currentG, currentB);
    }

    @Override
    public boolean onEstimateSize(int i, int i1) {
        HiLog.info(TAG, "onEstimateSize");
        int width = Component.EstimateSpec.getSize(i);
        int height = Component.EstimateSpec.getSize(i1);
        setEstimatedSize(
            Component.EstimateSpec.getChildSizeWithMode(width, width, Component.EstimateSpec.NOT_EXCEED),
            Component.EstimateSpec.getChildSizeWithMode(height, height, Component.EstimateSpec.NOT_EXCEED));
        return true;
    }

    @Override
    public void onDraw(Component component, Canvas canvas) {
        HiLog.info(TAG, "onDraw");
        onArrange();
        drawBorder(canvas);
        drawCenter(canvas);
        drawTick(canvas);
    }

    private void drawBorder(Canvas canvas) {
        mFloorPaint.setColor(new ohos.agp.utils.Color(mFloorColor));
        float radius = mCenterPoint.getPointX();
        canvas.drawCircle(mCenterPoint.getPointX(), mCenterPoint.getPointY(), radius * mFloorScale, mFloorPaint);
    }

    private void drawCenter(Canvas canvas) {
        mPaint.setColor(new ohos.agp.utils.Color(mUnCheckedColor));
        float radius = (mCenterPoint.getPointX() - mStrokeWidth) * mScaleVal;
        canvas.drawCircle(mCenterPoint.getPointX(), mCenterPoint.getPointY(), radius, mPaint);
    }

    private void drawTick(Canvas canvas) {
        if (mTickDrawing && isChecked()) {
            drawTickPath(canvas);
        }
    }

    private void drawTickPath(Canvas canvas) {
        mTickPath.reset();

        // draw left of the tick
        if (mDrewDistance < mLeftLineDistance) {
            float step = (mWidth / TWENTY) < THREE ? THREE : (mWidth / TWENTY);
            mDrewDistance += step;
            float stopX = mTickPoints[0].getPointX() + (mTickPoints[1].getPointX()
                - mTickPoints[0].getPointX()) * mDrewDistance / mLeftLineDistance;
            float stopY = mTickPoints[0].getPointY() + (mTickPoints[1].getPointY()
                - mTickPoints[0].getPointY()) * mDrewDistance / mLeftLineDistance;

            mTickPath.moveTo(mTickPoints[0].getPointX(), mTickPoints[0].getPointY());
            mTickPath.lineTo(stopX, stopY);
            canvas.drawPath(mTickPath, mTickPaint);

            if (mDrewDistance > mLeftLineDistance) {
                mDrewDistance = mLeftLineDistance;
            }
        } else {
            mTickPath.moveTo(mTickPoints[0].getPointX(), mTickPoints[0].getPointY());
            mTickPath.lineTo(mTickPoints[1].getPointX(), mTickPoints[1].getPointY());
            canvas.drawPath(mTickPath, mTickPaint);

            // draw right of the tick
            if (mDrewDistance < mLeftLineDistance + mRightLineDistance) {
                float stopX = mTickPoints[1].getPointX() + (mTickPoints[2].getPointX()
                    - mTickPoints[1].getPointX()) * (mDrewDistance - mLeftLineDistance) / mRightLineDistance;
                float stopY = mTickPoints[1].getPointY() - (mTickPoints[1].getPointY()
                    - mTickPoints[2].getPointY()) * (mDrewDistance - mLeftLineDistance) / mRightLineDistance;

                mTickPath.reset();
                mTickPath.moveTo(mTickPoints[1].getPointX(), mTickPoints[1].getPointY());
                mTickPath.lineTo(stopX, stopY);
                canvas.drawPath(mTickPath, mTickPaint);

                float step = Math.max(mWidth / TWENTY_INT, THREE);
                mDrewDistance += step;
            } else {
                mTickPath.reset();
                mTickPath.moveTo(mTickPoints[1].getPointX(), mTickPoints[1].getPointY());
                mTickPath.lineTo(mTickPoints[2].getPointX(), mTickPoints[2].getPointY());
                canvas.drawPath(mTickPath, mTickPaint);
            }
        }

        // invalidate
        if (mDrewDistance < mLeftLineDistance + mRightLineDistance) {
            new EventHandler(EventRunner.getMainEventRunner()).postTask(new Runnable() {
                @Override
                public void run() {
                    invalidate();
                }
            }, TEN_INT);
        }
    }

    /**
     * 相当于自定义的onLayout
     */
    public void onArrange() {
        mWidth = getEstimatedWidth();
        mStrokeWidth = mStrokeWidth == 0 ? getEstimatedWidth() / TEN_INT : mStrokeWidth;
        mStrokeWidth = Math.min(mStrokeWidth, getEstimatedWidth() / FIVE);
        mStrokeWidth = Math.max(mStrokeWidth, THREE);
        mCenterPoint.position[0] = mWidth / TWO;
        mCenterPoint.position[1] = getEstimatedHeight() / TWO;

        mTickPoints[0].position[0] = Math.round((float) getEstimatedWidth() / THIRTY * SEVEN);
        mTickPoints[0].position[1] = Math.round((float) getEstimatedHeight() / THIRTY * FOURTEEN);
        mTickPoints[1].position[0] = Math.round((float) getEstimatedWidth() / THIRTY * THIRTEEN);
        mTickPoints[1].position[1] = Math.round((float) getEstimatedHeight() / THIRTY * TWENTY_INT);
        mTickPoints[2].position[0] = Math.round((float) getEstimatedWidth() / THIRTY * TWEBTY_TWO);
        mTickPoints[2].position[1] = Math.round((float) getEstimatedHeight() / THIRTY * TEN_INT);

        mLeftLineDistance = (float) Math.sqrt(Math.pow(mTickPoints[1].position[0] - mTickPoints[0].position[0], TWO)
            + Math.pow(mTickPoints[1].position[1] - mTickPoints[0].position[1], TWO));
        mRightLineDistance = (float) Math.sqrt(Math.pow(mTickPoints[2].position[0] - mTickPoints[1].position[0], TWO)
            + Math.pow(mTickPoints[2].position[1] - mTickPoints[1].position[1], TWO));
        mTickPaint.setStrokeWidth(mStrokeWidth);
    }

    public void setOnCheckedChangeListener(OnCheckedChangeListener onCheckedChangeListener) {
        mOnCheckedChangeListener = onCheckedChangeListener;
    }

    /**
     * 控件选中状态的监听
     *
     * @since 2021-04-21
     */
    public interface OnCheckedChangeListener {
        /**
         * 选中状态变化
         *
         * @param checkBox 控件
         * @param isChecked 控件状态
         */
        void onCheckedChanged(SmoothCheckBox checkBox, boolean isChecked);
    }
}
